import { isUnaryTag } from "./utils.js";

export default function parseAstToStr(templateStr) {
  let root = null;
  // 备份模板,不要直接修改模板
  let html = templateStr;
  // 存放元素的 ast 对象
  const stack = [];
  // 遍历处理html字符串
  // debugger;
  while ((html = html.trim())) {
    // 匹配一下注释 当我们在页面上写入注释 会造成问题
    const zsIdx = html.indexOf("<!--");
    if (zsIdx === 0) {
      const endIdx = html.indexOf(">");
      html = html.slice(endIdx + 3);
      continue;
    }

    const startIdx = html.indexOf("<");
    if (startIdx === 0) {
      // 匹配到标签 <div  <img <input 等
      if (html.indexOf("</") === 0) {
        // 结束标签
        parseEnd();
      } else {
        // 开始标签
        parseStartTag();
      }
      // 在开始之前有一段文本
    } else if (startIdx > 0) {
      // 处理文本 <div>这里面的文本</div>
      const nextStartIdx = html.indexOf("<");
      if (stack.length) {
        // 如果stack不为空说明文字属于栈顶元素的文本节点
        processChars(html.slice(0, nextStartIdx));
      }
      // 处理完截取掉
      html = html.slice(nextStartIdx);
    }
  }

  return root;

  /*
   * 处理开始标签:
   */
  function parseStartTag() {
    // 找到结束标记
    const Inx = html.indexOf(">");
    // 截取开始标签内的所有内容 获取它的属性  div id="a" class="vb"
    const content = html.slice(1, Inx);
    // 更新截取后的html
    html = html.slice(Inx + 1);
    let tagName = "";
    let attrStr = "";
    // 找到 div id="a" class="vb" 中的第一个空格 用于区分标签名和属性
    const firstSpaceIdx = content.indexOf(" ");
    if (firstSpaceIdx === -1) {
      // 没有空格表示没有属性 <div></div>
      tagName = content;
    } else {
      // 标签名
      tagName = content.slice(0, firstSpaceIdx);
      // 属性名
      attrStr = content.slice(firstSpaceIdx + 1);
    }
    // 分离成数组  ['id="1"', 'class="2"']
    const attrs = attrStr ? attrStr.split(" ") : [];
    // 处理属性数组,得到map对象
    const attrsToMaps = parseAstToMap(attrs);
    // 生成AST对象
    const elementAst = generateToAst(tagName, attrsToMaps);
    if (!root) {
      root = elementAst;
    }
    // 将处理的ast对象插入stack栈中
    stack.push(elementAst);
    console.log('elementAst')
    // 是否是自闭合标签
    if (isUnaryTag(tagName)) {
      processElement();
    }
  }

  // 结束标记
  function parseEnd() {
    // 将闭合标签从html中截断  </div>  </body>  </xsxsxsxs>
    // 我们无法知道标签的长度 所以只能匹配后面的那个字符 > 而不是匹配 </
    const endIdx = html.indexOf(">");
    html = html.slice(endIdx + 1);
    // 截取完闭合标记后执行
    processElement();
  }

  // 数组转换map
  function parseAstToMap(attrArr) {
    const attrMap = {};
    for (const attr of attrArr) {
      // v-model="123"
      // 分离后变成 ['v-model', '"123"']
      const [attrName, attrValue] = attr.split("=");
      // debugger
      attrMap[attrName] = attrValue.replace(/"|'/g, "");
      // debugger;
    }

    return attrMap;
  }

  // 生成AST对象
  function generateToAst(tag, attrMap) {
    return {
      // 元素节点类型
      type: 1,
      // 标签名
      tag,
      // 原始属性对象
      rawAttr: attrMap,
      // 子节点
      children: [],
      // 父节点
      parent: [],
    };
  }

  /*
   *节点闭合标签被处理完执行
   *</div> 被截取掉以后执行
   *主要处理节点上面的 v-bind, v-on, v-model等等
   */
  function processElement() {
    // 栈顶标签处理完以后,弹出栈顶标签 并进一步处理
    const curPop = stack.pop();
    // 获取栈顶元素的属性
    const { rawAttr } = curPop;
    // console.log("rawAttr ===> ", rawAttr);
    // rawAttr 是未处理的attr属性列表 我们要将处理好的可以使用的绑定到 attrs属性列表中去
    curPop.attrs = {};
    // 获取属性名列表
    const attrKeysArr = Object.keys(rawAttr); // ['v-model', 'v-bind:title', 'v-on']等
    // console.log("attrKeysArr ===> ", attrKeysArr);
    // 遍历属性名
    for (const item of attrKeysArr) {
      // console.log("key ========> ", item);
      let modelReg = /v-model(.*?)/; // 匹配  v-model="value"
      let bindReg = /^(v-bind:|:)(.*?)/; // 匹配 v-bind:title 和 :title
      let onReg = /^(v-on:|@)(.*?)/; // 匹配 v-on:click 和 @click

      if (modelReg.test(item)) {
        // 如果有双向绑定
        let bindKey = item.replace(modelReg, "");
        processVModel(curPop, bindKey);
      } else if (bindReg.test(item)) {
        // 如果有v-bind绑定  curPop key value
        let key = item.replace(bindReg, "");
        // console.log("key bindReg ===>  ", key, `v-bind:${key}`);
        processVBind(curPop, key, rawAttr[`v-bind:${key}`]);
      } else if (onReg.test(item)) {
        // 如果有v-on绑定 curPop key value
        let key = item.replace(onReg, "");
        // console.log("key onReg ===>  ", key);
        processVOn(curPop, key, rawAttr[`v-on:${key}`]);
      } else {
        // 如果是常规元素比如 name、id、style静态
        curPop.attrs[item] = rawAttr[item];
      }
    }
    // 处理完属性之后，处理插槽
    processSlotContent(curPop);
    // 处理完属性后, 将处理过的节点插入到他的父节点childeren里
    const arrlen = stack.length;
    if (arrlen) {
      // 子元素插入到父节点childern
      stack[arrlen - 1].children.push(curPop);
      // 子节点的parent指向父节点
      curPop.parent = stack[arrlen - 1];
      // 如果节点存在 slotName 说明该节点是组件传递给插槽的内容
      // 将插槽信息放到组件节点的 rawAttr.scopedSlots 对象上
      // 这些信息会再生成插槽的vnode时 renderSlot方法中 会用到
      if (curPop.slotName) {
        // parent 指向插槽父元素
        const { parent, slotName, scopeSlot, children } = curPop;
        // 插槽的信息
        const slotInfo = {
          slotName, // 插槽名称
          scopeSlot, // 插槽值
          children: children.map((item) => {
            delete item.parent; // 防止爆栈
            return item;
          }),
        };
        // 如果父组件存在这个属性 表示有自定义得
        if (parent.rawAttr.scopedSlots) {
          parent.rawAttr.scopedSlots[curPop.slotName] = slotInfo;
        } else {
          // 不存在这个属性
          parent.rawAttr.scopedSlots = { [curPop.slotName]: slotInfo };
        }
      }
    } else {
      // 如果是栈顶 也就是最外层的节点
      // 不做处理
    }
  }

  // 处理文本
  function processChars(text) {
    if (!text.trim()) return;
    // 构建文本节点的AST对象
    const textAST = {
      type: 3,
      text,
    };
    // 文本如果有引入变量 {{name}}
    if (text.match(/{{(.*)}}/)) {
      textAST.expression = RegExp.$1.trim();
    }
    // 将文本节点放到栈顶元素的肚子里
    stack[stack.length - 1].children.push(textAST);
  }

  /*
   * 处理属性上的v-model双向绑定
   * 将处理结果放置到 curPop.attrs 的 VModel 上
   * 使用场景
   * <select v-model="xx"></select>
   * <input type="text" v-model="xx"></input>
   * <input type="checkbox" v-model="xx"></input>
   */
  function processVModel(curPop, bindKey) {
    const { tag, attrs, rawAttr } = curPop;
    const { type, "v-model": vModelValue } = rawAttr;
    if (tag == "input") {
      // 输入框  checkbox'
      if (/text/.test(type)) {
        attrs.vModel = { tag, type: "text", value: vModelValue };
      }
      if (/checkbox/.test(type)) {
        attrs.vModel = { tag, type: "checkbox", value: vModelValue };
      }
    } else if (tag == "textarea") {
      // 文本域
      attrs.vModel = { tag, type: "textarea", value: vModelValue };
    } else if (tag == "select") {
      // 选择框
      attrs.vModel = { tag, value: vModelValue };
    }
  }

  /*
   * 处理属性上的v-model双向绑定
   * 将处理结果放置到 curPop.attrs 的 VBind 上
   * <div v-bind:style="xxx" v-bind:id="xxx"></div>
   */
  function processVBind(curPop, bindKey, bindValue) {
    curPop.attrs.vBind = {
      [bindKey]: bindValue,
    };
  }

  /*
   * 处理属性上的v-model双向绑定
   * 将处理结果放置到 curPop.attrs 的 VOn 上
   * <div v-on:click=""></div>
   */
  function processVOn(curPop, vOnKey, vOnValue) {
    curPop.attrs.vOn = { [vOnKey]: vOnValue };
  }

  /**
   * <template v-slot:default="scopeSlot">
   *   <div>{{ scopeSlot }}</div>
   * </template>
   * 处理插槽，如果是slot 就处理
   * @param {*} curPop
   * @returns
   */
  function processSlotContent(el) {
    // 注意：template必须只能是组件的跟元素
    const { tag, rawAttr } = el;
    if (tag === "template") {
      const attrMap = rawAttr;
      for (const key in attrMap) {
        if (key.match(/v-slot:(.*)/)) {
          // 如果匹配到了表示组件有v-slot表示
          // 获取插槽名称
          const slotName = (el.slotName = RegExp.$1);
          // 获取插槽值作用域的值  { v-slot:title: 'name: }
          el.scopeSlot = attrMap[`v-slot:${slotName}`];
          // 一个标签只可能有一个 v-slot
          return;
        }
      }
    }
  }
}
